
package VideoRent.Entrada;

import java.io.BufferedWriter;
import java.io.BufferedReader;
import java.util.Calendar;
import java.util.regex.*;

import VideoRent.*;
import VideoRent.Enum.*;
import VideoRent.Caja.*;

public class AccionesCliente extends VideoRentEntrada{

    private ElementosTienda elementosTienda;

    private Caja caja;

    private BufferedWriter bufferEscrituraEmpleados;

    private BufferedWriter bufferEscrituraFacturas;

    private Pattern patronCabecera = null;

    private Pattern patronFecha = null;

    private Pattern patronAsociarCliente = null;

    private Pattern patronCambiarTarjeta = null;

    private Pattern patronCompraClienteAsociado = null;

    private Pattern patronAlquilerClienteAsociado = null;

    private Pattern patronPagoClienteAsociado = null;

    private Pattern patronAbandonoClienteAsociado = null;

    private Pattern patronDevolucionClienteAsociado = null;

    private Pattern patronDomicilioClienteAsociado = null;

    private Pattern patronCompraCliente = null;

    private Pattern patronPagoCliente = null;

    private Pattern patronAbandonoCliente = null;

    public AccionesCliente(ElementosTienda elementosTienda, BufferedWriter bufferEscrituraEmpleados, BufferedWriter bufferEscrituraFacturas){
        this.elementosTienda = elementosTienda;
        this.bufferEscrituraEmpleados = bufferEscrituraEmpleados;
        this.bufferEscrituraFacturas = bufferEscrituraFacturas;
    }


    /**
     * Lee y analiza las acciones de los clientes de un buffer de entrada
     * 
     * @param bufferLectura Buffer que contiene las acciones de los clientes en cada línea
     */
    public void leer(BufferedReader bufferLectura)
        throws VideoRentEntradaException
    {
        String linea = leerLinea(bufferLectura);
        if (null == linea){
            throw new VideoRentEntradaException("Archivo mal formateado: no vino la primera linea con el número de días y el número de acciones");
        }

        Matcher buscador = getPatronCabecera().matcher(linea);
        if (!buscador.find()){
            throw new VideoRentEntradaException("Archivo mal formateado: primera línea en formato incorrecto '" + linea + "'");
        }

        int numeroDias;
        try{
            numeroDias = Integer.parseInt(buscador.group(1));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Número de días inválido: '" + buscador.group(1) + "'");
        }

        int numeroAcciones;
        try{
            numeroAcciones = Integer.parseInt(buscador.group(2));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Número de acciones inválido: '" + buscador.group(2) + "'");
        }

        linea = leerLinea(bufferLectura);
        if (null == linea){
            throw new VideoRentEntradaException("Archivo mal formateado: no vino la segunda linea con la fecha del día inicial");
        }

        buscador = getPatronFecha().matcher(linea);
        if (!buscador.find()){
            throw new VideoRentEntradaException("Archivo mal formateado: segunda línea en formato incorrecto '" + linea + "'");
        }

        int dia, mes, ano;
        try{
            dia = Integer.parseInt(buscador.group(1));
            mes = Integer.parseInt(buscador.group(2));
            ano = Integer.parseInt(buscador.group(3));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Fecha inválida: '" + buscador.group(0) + "'");
        }

        Calendar fecha = Calendar.getInstance();
        fecha.set(ano, mes, dia, 1, 0, 0);

        this.caja = new Caja(this.elementosTienda, this.bufferEscrituraEmpleados, this.bufferEscrituraFacturas, fecha);

        for (int i = 0; i < numeroDias + numeroAcciones; ++ i){
            linea = leerLinea(bufferLectura);
            if (null == linea){
                throw new VideoRentEntradaException("Se esperaban '" + (numeroDias + numeroAcciones) +
                    "' líneas entre separadores de día y acciones. Se encontraron '" + i + "' líneas.");
            }

            if ("#".equals(linea)){
                try{
                    caja.cambioDia();
                }catch(VideoRentAccionException e){
                    throw new VideoRentEntradaException("No se pudo procesar el cambio de día: " + e.getMessage());
                }
                continue;
            }

            buscador = getPatronAsociarCliente().matcher(linea);
            if (buscador.find()){
                ejecutarAsociarCliente(buscador);
                continue;
            }

            buscador = getPatronCambiarTarjeta().matcher(linea);
            if (buscador.find()){
                ejecutarCambiarTarjeta(buscador);
                continue;
            }

            buscador = getPatronCompraClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarCompraClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronAlquilerClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarAlquilerClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronPagoClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarPagoClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronAbandonoClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarAbandonoClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronDevolucionClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarDevolucionClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronDomicilioClienteAsociado().matcher(linea);
            if (buscador.find()){
                ejecutarDomicilioClienteAsociado(buscador);
                continue;
            }

            buscador = getPatronCompraCliente().matcher(linea);
            if (buscador.find()){
                ejecutarCompraCliente(buscador);
                continue;
            }

            buscador = getPatronPagoCliente().matcher(linea);
            if (buscador.find()){
                ejecutarPagoCliente(buscador);
                continue;
            }

            buscador = getPatronAbandonoCliente().matcher(linea);
            if (buscador.find()){
                ejecutarAbandonoCliente(buscador);
                continue;
            }

            throw new VideoRentEntradaException("La línea '" + linea + "' no corresponde con la expresión regular de ninguna acción de cliente");
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * la cabecera. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la cabecera
     */
    protected Pattern getPatronCabecera(){
        if (null == patronCabecera){
            //Cantidad de clientes y próximo código correlativo
            patronCabecera = Pattern.compile("^(\\d+)\\s*(\\d+)$");
        }
        return patronCabecera;
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * la fecha inical de los clientes
     * 
     * @return Expresión regular compilada que analiza la fecha
     */
    protected Pattern getPatronFecha(){
        if (null == patronFecha){
            //Fecha de comienzo
            patronFecha = Pattern.compile("^(0?\\d{1,2})/0?(\\d{1,2})/(\\d{4})$");
        }
        return patronFecha;
    }


    /**
     * Lee y analiza los parámetros de una asociación de cliente. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarAsociarCliente(Matcher buscadorAsociarCliente)
        throws VideoRentEntradaException
    {

        int cedula;
        try{
            cedula = Integer.parseInt(buscadorAsociarCliente.group(1));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Cédula inválida: '" + buscadorAsociarCliente.group(1) + "'");
        }

        int telefono;
        try{
            telefono = Integer.parseInt(buscadorAsociarCliente.group(4));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Teléfono inválido: '" + buscadorAsociarCliente.group(4) + "'");
        }

        int cvv;
        try{
            cvv = Integer.parseInt(buscadorAsociarCliente.group(8));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Código de seguridad inválido: '" + buscadorAsociarCliente.group(8) + "'");
        }

        int mesVencimiento;
        try{
            mesVencimiento = Integer.parseInt(buscadorAsociarCliente.group(9));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Mes de fecha de vencimiento de tarjeta de crédito: '" + buscadorAsociarCliente.group(9) + "'");
        }

        int anoVencimiento;
        try{
            anoVencimiento = Integer.parseInt(buscadorAsociarCliente.group(10));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Año de fecha de vencimiento de tarjeta de crédito: '" + buscadorAsociarCliente.group(10) + "'");
        }
        Calendar fechaVencimiento = Calendar.getInstance();
        fechaVencimiento.set(anoVencimiento, mesVencimiento, 1, 0, 0, 0);

        Membresia membresia;
        String membresiaString = buscadorAsociarCliente.group(11).toLowerCase();
        if ("basica".equals(membresiaString)){
            membresia = Membresia.BASICA;
        }else if ("premium".equals(membresiaString)){
            membresia = Membresia.PREMIUM;
        }else{
            throw new VideoRentEntradaException("Tipo de membresía inválido: '" + buscadorAsociarCliente.group(11));
        }

        try{
            caja.asociar(cedula,
                //Nombre
                buscadorAsociarCliente.group(2),
                //Apellido
                buscadorAsociarCliente.group(3), telefono,
                //Dirección
                buscadorAsociarCliente.group(5),
                //Número de tarjeta
                buscadorAsociarCliente.group(6),
                //Banco
                buscadorAsociarCliente.group(6), cvv, fechaVencimiento, membresia);
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo asociar el cliente de cédula '" + cedula + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * la asociación de cliente. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la asociación de cliente
     */
    protected Pattern getPatronAsociarCliente(){
        if (null == patronAsociarCliente){
            patronAsociarCliente = Pattern.compile(
                //Código de operación
                "^a\\s*" +
                //Cédula
                "&\\s*(\\d+)\\s*" +
                //Nombre
                "&\\s*(\\w+)\\s*" +
                //Apellido
                "&\\s*(\\w+)\\s*" +
                //Teléfono
                "&\\s*(\\d+)\\s*" +
                //Dirección
                "&\\s*([^&]+?)\\s*" +
                //Tarjeta de crédito
                "&\\s*(\\d+)\\s*" +
                //Banco
                "&\\s*([^&]+?)\\s*" +
                //Código de seguridad
                "&\\s*(\\d{3})\\s*" +
                //Fecha de vencimiento
                "&\\s*0?(\\d{1,2})/(\\d{4})\\s*" +
                //Tipo de membresía
                "&\\s*([^&]+?)\\s*$");
        }
        return patronAsociarCliente;
    }


    /**
     * Lee y analiza los parámetros de un cambio de tarjeta. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarCambiarTarjeta(Matcher buscadorCambiarTarjeta)
        throws VideoRentEntradaException
    {

        int cvv;
        try{
            cvv = Integer.parseInt(buscadorCambiarTarjeta.group(4));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Código de seguridad inválido: '" + buscadorCambiarTarjeta.group(4) + "'");
        }

        int mesVencimiento;
        try{
            mesVencimiento = Integer.parseInt(buscadorCambiarTarjeta.group(5));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Mes de fecha de vencimiento de tarjeta de crédito: '" + buscadorCambiarTarjeta.group(5) + "'");
        }

        int anoVencimiento;
        try{
            anoVencimiento = Integer.parseInt(buscadorCambiarTarjeta.group(6));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Año de fecha de vencimiento de tarjeta de crédito: '" + buscadorCambiarTarjeta.group(6) + "'");
        }
        Calendar fechaVencimiento = Calendar.getInstance();
        fechaVencimiento.set(anoVencimiento, mesVencimiento, 1, 0, 0, 0);

        try{
            caja.actualizarTarjeta(
                //Código cliente
                buscadorCambiarTarjeta.group(1),
                //Número tarjeta
                buscadorCambiarTarjeta.group(2),
                //Banco
                buscadorCambiarTarjeta.group(3), cvv, fechaVencimiento);
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo cambiar la tarjeta del cliente de código '" +
                buscadorCambiarTarjeta.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el cambio de tarjeta. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el cambio de tarjeta
     */
    protected Pattern getPatronCambiarTarjeta(){
        if (null == patronCambiarTarjeta){
            patronCambiarTarjeta = Pattern.compile(
                //Código de operación
                "^t\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Tarjeta de crédito
                "&\\s*(\\d+)\\s*" +
                //Banco
                "&\\s*([^&]+?)\\s*" +
                //Código de seguridad
                "&\\s*(\\d{3})\\s*" +
                //Fecha de vencimiento
                "&\\s*0?(\\d{1,2})/(\\d{4})\\s*$");
        }
        return patronCambiarTarjeta;
    }


    /**
     * Lee y analiza los parámetros de una compra de cliente asociado. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarCompraClienteAsociado(Matcher buscadorCompraClienteAsociado)
        throws VideoRentEntradaException
    {
        try{
            caja.compra(
                //Código cliente
                buscadorCompraClienteAsociado.group(1),
                //Código artículo
                buscadorCompraClienteAsociado.group(2));
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo comprar el artículo '" + buscadorCompraClienteAsociado.group(2) +
                 "' para el cliente con código '" + buscadorCompraClienteAsociado.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * la compra de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la compra de cliente asociado
     */
    protected Pattern getPatronCompraClienteAsociado(){
        if (null == patronCompraClienteAsociado){
            patronCompraClienteAsociado = Pattern.compile(
                //Código de operación
                "^c\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Código de artículo
                "&\\s*((?:P|S|R|E)\\d+)\\s*$");
        }
        return patronCompraClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de un alquiler de cliente asociado. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarAlquilerClienteAsociado(Matcher buscadorAlquilerClienteAsociado)
        throws VideoRentEntradaException
    {
        try{
            caja.alquiler(
                //Código cliente
                buscadorAlquilerClienteAsociado.group(1),
                //Código artículo
                buscadorAlquilerClienteAsociado.group(2));
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo alquilar el artículo '" + buscadorAlquilerClienteAsociado.group(2) +
                 "' para el cliente con código '" + buscadorAlquilerClienteAsociado.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el alquiler de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el alquiler de cliente asociado
     */
    protected Pattern getPatronAlquilerClienteAsociado(){
        if (null == patronAlquilerClienteAsociado){
            patronAlquilerClienteAsociado = Pattern.compile(
                //Código de operación
                "^r\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Código de artículo
                "&\\s*((?:P|S|R|E)\\d+)\\s*$");
        }
        return patronAlquilerClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de un pago de cliente asociado. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarPagoClienteAsociado(Matcher buscadorPagoClienteAsociado)
        throws VideoRentEntradaException
    {

        int monto;
        try{
            monto = Integer.parseInt(buscadorPagoClienteAsociado.group(2));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Monto inválido: '" + buscadorPagoClienteAsociado.group(2) + "'");
        }
        try{
            caja.pagar(
                //Código cliente
                buscadorPagoClienteAsociado.group(1),
                //Monto a pagar
                monto);
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo pagar '" + monto +
                 "' para el cliente con código '" + buscadorPagoClienteAsociado.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el pago de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el pago de cliente asociado
     */
    protected Pattern getPatronPagoClienteAsociado(){
        if (null == patronPagoClienteAsociado){
            patronPagoClienteAsociado = Pattern.compile(
                //Código de operación
                "^p\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Monto
                "&\\s*(\\d+)\\s*$");
        }
        return patronPagoClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de un pago de cliente asociado.
     * 
     * Este método existe sólo por claridad. El efecto sobre el sistema es nulo.
     */
    protected void ejecutarAbandonoClienteAsociado(Matcher buscadorAbandonoClienteAsociado)
        throws VideoRentEntradaException
    {

    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el abandono de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el abandono de cliente asociado
     */
    protected Pattern getPatronAbandonoClienteAsociado(){
        if (null == patronAbandonoClienteAsociado){
            patronAbandonoClienteAsociado = Pattern.compile(
                //Código de operación
                "^b\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*$");
        }
        return patronAbandonoClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de una devolución de cliente asociado. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarDevolucionClienteAsociado(Matcher buscadorDevolucionClienteAsociado)
        throws VideoRentEntradaException
    {
        try{
            caja.devolverCaja(
                //Código cliente
                buscadorDevolucionClienteAsociado.group(1),
                //Código artículo
                buscadorDevolucionClienteAsociado.group(2));
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo devolver el artículo '" + buscadorDevolucionClienteAsociado.group(2) +
                 "' para el cliente con código '" + buscadorDevolucionClienteAsociado.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el abandono de devolución de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la devolución de cliente asociado
     */
    protected Pattern getPatronDevolucionClienteAsociado(){
        if (null == patronDevolucionClienteAsociado){
            patronDevolucionClienteAsociado = Pattern.compile(
                //Código de operación
                "^d\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Código de artículo
                "&\\s*((?:P|S|R|E)\\d+)\\s*$");
        }
        return patronDevolucionClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de una devolución a domicilio de cliente asociado. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarDomicilioClienteAsociado(Matcher buscadorDomicilioClienteAsociado)
        throws VideoRentEntradaException
    {
        try{
            caja.devolverBuscar(
                //Código cliente
                buscadorDomicilioClienteAsociado.group(1),
                //Código artículo
                buscadorDomicilioClienteAsociado.group(2));
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo devolver (a domicilio) el artículo '" + buscadorDomicilioClienteAsociado.group(2) +
                 "' para el cliente con código '" + buscadorDomicilioClienteAsociado.group(1) + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * el abandono de devolución a domicilio de cliente asociado. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la devolución a domicilio de cliente asociado
     */
    protected Pattern getPatronDomicilioClienteAsociado(){
        if (null == patronDomicilioClienteAsociado){
            patronDomicilioClienteAsociado = Pattern.compile(
                //Código de operación
                "^e\\s*" +
                //Código de cliente
                "&\\s*((?:P|B)\\d+)\\s*" +
                //Código de artículo
                "&\\s*((?:P|S|R|E)\\d+)\\s*$");
        }
        return patronDomicilioClienteAsociado;
    }


    /**
     * Lee y analiza los parámetros de una compra de cliente. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarCompraCliente(Matcher buscadorCompraCliente)
        throws VideoRentEntradaException
    {

        int cedula;
        try{
            cedula = Integer.parseInt(buscadorCompraCliente.group(1));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Cédula inválida: '" + buscadorCompraCliente.group(1) + "'");
        }

        int telefono;
        try{
            telefono = Integer.parseInt(buscadorCompraCliente.group(3));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Teléfono inválido: '" + buscadorCompraCliente.group(3) + "'");
        }

        try{
            caja.compra(cedula,
                //Nombre
                buscadorCompraCliente.group(2), telefono,
                //Código artículo
                buscadorCompraCliente.group(4));
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo comprar el artículo '" + buscadorCompraCliente.group(4) +
                 "' para el cliente con cédula '" + cedula + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * una compra de cliente. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza la compra de cliente
     */
    protected Pattern getPatronCompraCliente(){
        if (null == patronCompraCliente){
            patronCompraCliente = Pattern.compile(
                //Código de operación
                "^c\\s*" +
                //Cédula de cliente
                "&\\s*(\\d+)\\s*" +
                //Nombre
                "&\\s*(\\w+)\\s*" +
                //Teléfono
                "&\\s*(\\d+)\\s*" +
                //Código de artículo
                "&\\s*((?:P|S|R|E)\\d+)\\s*$");
        }
        return patronCompraCliente;
    }


    /**
     * Lee y analiza los parámetros de un pago de cliente. A su vez
     * pasa el control a la caja con el método apropiado
     */
    protected void ejecutarPagoCliente(Matcher buscadorPagoCliente)
        throws VideoRentEntradaException
    {

        int cedula;
        try{
            cedula = Integer.parseInt(buscadorPagoCliente.group(1));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Cédula inválida: '" + buscadorPagoCliente.group(1) + "'");
        }

        int monto;
        try{
            monto = Integer.parseInt(buscadorPagoCliente.group(2));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Monto inválido: '" + buscadorPagoCliente.group(2) + "'");
        }

        try{
            caja.pagar(
                //Cédula cliente
                cedula,
                //Monto a pagar
                monto);
        }catch(VideoRentAccionException e){
            throw new VideoRentEntradaException("No se pudo pagar '" + monto +
                 "' para el cliente con la cédula '" + cedula + "': " + e.getMessage());
        }
    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * un pago de cliente. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el pago de cliente
     */
    protected Pattern getPatronPagoCliente(){
        if (null == patronPagoCliente){
            patronPagoCliente = Pattern.compile(
                //Código de operación
                "^p\\s*" +
                //Cédula de cliente
                "&\\s*(\\d+)\\s*" +
                //Monto
                "&\\s*(\\d+)\\s*$");
        }
        return patronPagoCliente;
    }


    /**
     * Lee y analiza los parámetros de un abandono de cliente.
     * 
     * Este método existe sólo por claridad. El efecto sobre el sistema es nulo.
     */
    protected void ejecutarAbandonoCliente(Matcher buscadorAbandonoCliente)
        throws VideoRentEntradaException
    {

        int cedula;
        try{
            cedula = Integer.parseInt(buscadorAbandonoCliente.group(1));
        }catch(NumberFormatException e){
            throw new VideoRentEntradaException("Cédula inválida: '" + buscadorAbandonoCliente.group(1) + "'");
        }

    }


    /**
     * Genera el patrón compilado de la expresión regular que analiza
     * un abandono de cliente. Se asegura de generarla sólo una vez.
     * 
     * @return Expresión regular compilada que analiza el abandono de cliente
     */
    protected Pattern getPatronAbandonoCliente(){
        if (null == patronAbandonoCliente){
            patronAbandonoCliente = Pattern.compile(
                //Código de operación
                "^b\\s*" +
                //Cédula de cliente
                "&\\s*(\\d+)\\s*$");
        }
        return patronAbandonoCliente;
    }
}